package cn.xinfei.xdecision.data.core.frame.executors.groovy;

import cn.xinfei.xdecision.common.model.datax.consts.VarPrometheusTagsConst;
import cn.xinfei.xdecision.common.model.datax.consts.VariableConst;
import cn.xinfei.xdecision.common.model.datax.datamanage.v2.Variable;
import cn.xinfei.xdecision.common.model.datax.datamanage.v2.VariableCallLog;
import cn.xinfei.xdecision.common.model.datax.enums.*;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import cn.xinfei.xdecision.common.service.datax.datamanage.v2.VariableParamService;
import cn.xinfei.xdecision.common.service.datax.datamanage.v2.VariableScriptService;
import cn.xinfei.xdecision.common.service.datax.datamanage.v2.VariableService;
import cn.xinfei.xdecision.data.core.cache.VarScriptCache;
import cn.xinfei.xdecision.data.core.frame.executors.IExecutorScriptService;
import cn.xinfei.xdecision.data.core.frame.executors.collect.VariableHelper;
import cn.xinfei.xdecision.data.core.util.VariableCallLogUtils;
import cn.xinfei.xdecision.data.core.vo.GroovyExecutorResultEntity;
import com.alibaba.fastjson.JSONObject;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import com.google.common.collect.Maps;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class GroovyExecutorService implements IExecutorScriptService<GroovyExecutorResultEntity> {

    @Autowired
    VariableScriptService varScriptService;

    @Autowired
    VariableService variableService;

    @Autowired
    VariableParamService variableParamService;


    @Autowired
    GroovySupport groovySupport;

    @Autowired
    VarScriptCache varScriptCache;

    @Override
    public GroovyExecutorResultEntity run(String varCode,
                                          Map<String, Object> inputParam,
                                          Map varDependParamMap,
                                          ExecutorEnum executorStage,
                                          Map<String, Object> executorResult,
                                          CollectStageEnum stage,
                                          String requestId) {

        try {
            log.info("start run {}-executor varCode: {}", executorStage.name().toLowerCase(), JSONObject.toJSONString(varCode));
            GroovyExecutorResultEntity groovyExecutorResult = runGroovyExecutorResult(varCode,
                    inputParam, executorStage, varDependParamMap, executorResult, stage, requestId);
            return groovyExecutorResult;
        } catch (Exception e) {
            GroovyExecutorResultEntity resultEntity = new GroovyExecutorResultEntity();
            Variable var = variableService.findVarTypeByVarCodes(varCode);
            log.error("error in run {}-executor, varCode: {},inputParam: {}", executorStage.name().toLowerCase(), varCode, JSONObject.toJSONString(inputParam), e);
            resultEntity.setGroovyVarResult(VariableHelper.fillVarWithDefaultValueWhenRespEmpty(Collections.singleton(var)));
            resultEntity.setCode(String.valueOf(XDecisionDataExceptionType.DATA_GROOVY_EXECUTOR_ERROR.code));
            resultEntity.setMessage(XDecisionDataExceptionType.DATA_GROOVY_EXECUTOR_ERROR.text);
            return resultEntity;
        }finally {
            MDC.remove(VariableConst.HANDLE_NAME);
            MDC.remove(VariableConst.COLLECT_STAGE);
            MDC.remove(VariableConst.REQUEST_ID);
            MDC.clear();
        }
    }

    private GroovyExecutorResultEntity runGroovyExecutorResult(String varCode,
                                                               Map<String, Object> inputParam,
                                                               ExecutorEnum executorStage,
                                                               Map<String, List<String>> deriveDependParamMap,
                                                               Map<String, Object> executorResult,
                                                               CollectStageEnum stage,
                                                               String requestId) {

        Transaction catT = Cat.newTransaction(VarCatTypeEnum.GROOVY_EXECUTOR_EXECUTE.getCode(), VarCatTypeEnum.GROOVY_EXECUTOR_EXECUTE.getName());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        GroovyExecutorResultEntity executorResultEntity = new GroovyExecutorResultEntity();
        executorResultEntity.setExecType(executorStage.name());
        Map<String, Object> groovyExecutorResult = Maps.newConcurrentMap();
        Map<String, Object> varParamAndValMap;
        Optional<Map<String, String>> scriptCache = Optional.ofNullable(varScriptCache.loadVarScriptMetaData());
        String script;
        Set<String> varParamCode = new HashSet<>(deriveDependParamMap.get(varCode));
        try {
            if (scriptCache.isPresent()) {
                log.info("requestId = {} GroovyExecutor 查询缓存 VarScriptCache！", requestId);
                script = Optional.ofNullable(scriptCache.get().get(varCode))
                        .orElseGet(() -> {
                            log.info("requestId = {} VarScriptCache缓存 未查得groovy脚本", requestId);
                            return varScriptService.findScriptByVariableCode(varCode);
                        });
            } else {
                script = varScriptService.findScriptByVariableCode(varCode);
            }
            switch (executorStage) {
                case GROOVY_PRE:
                    varParamAndValMap = Optional.ofNullable(varParamCode).orElse(Collections.emptySet())
                            .stream()
                            .distinct()
                            .map(var -> Pair.of(var, inputParam.get(var)))
                            .filter(pair -> Objects.nonNull(pair.getValue()))
                            .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
                    break;
                case GROOVY_AFTER:
//                    executorResult.putAll(inputParam);
                    varParamAndValMap = Optional.ofNullable(varParamCode).orElse(Collections.emptySet())
                            .stream()
                            .distinct()
                            .map(var -> Pair.of(var, executorResult.get(var)))
                            .filter(pair -> Objects.nonNull(pair.getValue()))
                            .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
                    break;
                default:
                    throw new XDecisionDataException(XDecisionDataExceptionType.DATA_NO_SUCH_EXECUTOR_ERROR, "no such executor for: " + executorStage.name());

            }
//            Object groovyResult = groovy.executeForObject(script, varParamAndValMap);
            Object groovyResult = groovySupport.parseExprWithAt(script, varParamAndValMap);
            groovyExecutorResult.put(varCode, groovyResult);
            log.info("requestId = {} varCode: {} groovyExecutorResult: {}", requestId, varCode, JSONObject.toJSONString(groovyExecutorResult));
            executorResultEntity.setGroovyVarResult(groovyExecutorResult);
//            VariableCallLog variableCallLog = VariableCallLogUtils.buildVarCallLog(varCode, inputParam, String.valueOf(groovyResult),
//                    -1L,
//                    VariableEnum.DERIVE_VAR,
//                    stage,
//                    SourceType.DERIVE,
//                    stopWatch.getStartTime(),
//                    requestId);
//            executorResultEntity.setVariableCallLog(variableCallLog);
            catT.setStatus(Transaction.SUCCESS);
            Metrics.counter(VarPrometheusTagsConst.GROOVY_HANDLE_SUCCESS_COUNT,
                    VarPrometheusTagsConst.MetricsFliedTags.VAR_CODE, varCode,
                    VarPrometheusTagsConst.MetricsFliedTags.COLLECT_STAGE, stage.getCode(),
                    VarPrometheusTagsConst.MetricsFliedTags.GROOVY_STAGE, executorStage.getCode()).increment();
            return executorResultEntity;
        } catch (Exception e) {
            log.error("requestId = {}  varCode: {} 执行groovy异常：msg={}",
                    requestId,
                    varCode,
                    e.getMessage(), e);
            Cat.logError(e);
            catT.setStatus(e);
            Metrics.counter(VarPrometheusTagsConst.GROOVY_HANDLE_FAIL_COUNT,
                    VarPrometheusTagsConst.MetricsFliedTags.VAR_CODE, varCode,
                    VarPrometheusTagsConst.MetricsFliedTags.COLLECT_STAGE, stage.getCode(),
                    VarPrometheusTagsConst.MetricsFliedTags.GROOVY_STAGE, executorStage.getCode()).increment();
            throw new XDecisionDataException(XDecisionDataExceptionType.DATA_GROOVY_EXECUTOR_ERROR, executorStage.name() + "_DataSource 执行器执行异常", e);
        } finally {
            stopWatch.stop();
            log.info("requestId = {}  varCode: {} 任务执行 {}_DataSource 总耗时: {}", requestId, varCode, executorStage.name(), stopWatch.getTime() + " MillSeconds");
            catT.complete();
            Metrics.counter(VarPrometheusTagsConst.GROOVY_HANDLE_COUNT,
                    VarPrometheusTagsConst.MetricsFliedTags.VAR_CODE, varCode,
                    VarPrometheusTagsConst.MetricsFliedTags.COLLECT_STAGE, stage.getCode(),
                    VarPrometheusTagsConst.MetricsFliedTags.GROOVY_STAGE, executorStage.getCode()).increment();

            Set<Tag> tagList = new HashSet<>();
            tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.VAR_CODE, varCode));
            tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.COLLECT_STAGE, stage.getCode()));
            tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.GROOVY_STAGE, executorStage.getCode()));
            Metrics.gauge(VarPrometheusTagsConst.GROOVY_HANDLE_DURATION, tagList, stopWatch.getTime());
        }
    }


}
